package com.ruoyi.handler.websocket;

import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.constant.WebSocketMsgContants;
import com.ruoyi.common.core.domain.entity.socket.bean.*;
import com.ruoyi.common.core.domain.entity.system.BoatAlarmInfo;
import com.ruoyi.common.core.domain.entity.system.ScenicOperationDynamicInfo;
import com.ruoyi.common.core.domain.entity.wsbean.*;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.manager.WebSocketChannelManager;
import com.ruoyi.system.service.IScenicDailyIncomeService;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Date;
import java.util.List;
import java.util.Objects;

@Slf4j
@Component
public class WebSocketMessageHandler {

    /**
     * 小程序向后端发送websocket心跳包的间隔时长,单位：秒。
     */
    @Value("${miniprogram.wsHeartBeatDuration}")
    private int wsHeartBeatDuration;

    /**
     * 持续未收到心跳包后断开连接的最大等待时长。
     */
    private static int maxHeartBeatCloseDuration;


    @PostConstruct
    public void setWsHeartBeatDuration() {
        maxHeartBeatCloseDuration = this.wsHeartBeatDuration * 1000 + 5000;
    }

    /**
     * 发送文本消息。
     *
     * @param channel
     * @param code
     * @param msg
     * @param msgObj
     */
    private static void sendMsg(Channel channel, int code, String msg, Object msgObj) {
        ResponseTextMsg resTextMsg = new ResponseTextMsg();
        resTextMsg.setCode(code);
        resTextMsg.setMsg(msg);
        resTextMsg.setTimestamp(String.valueOf(new Date().getTime()));
        resTextMsg.setData(msgObj);

//        log.info("sendMsg resTextMsg= {}", resTextMsg);

        if (channel != null) {
            if (channel.isActive()) {
                channel.writeAndFlush(new TextWebSocketFrame(JSONObject.toJSONString(resTextMsg))).addListener((ChannelFutureListener) future -> {
                    if (future.isSuccess()) {
//                        log.info("sendMsg 发送消息成功！");
                    } else {
                        log.warn("sendMsg 发送消息失败！ cause: {}", future.cause().getMessage());
                    }
                });

            } else {
                channel.closeFuture().addListener((ChannelFutureListener) future -> {
                    log.warn("当前channel[{}]已失活，无法发送消息，已将其关闭！", channel);
                });
            }
        } else {
            log.error("sendMsg channel为null，发送消息失败！");
        }
    }

    /**
     * 服务器给websocket客户端回应pong心跳。
     */
    public static void pong(Channel channel) {
        sendMsg(channel, WebSocketMsgContants.MSG_PONG, "", new PongMsg("ws-pong"));
    }

    /**
     * 服务器发送主动关闭连接的消息报文。该方法会在发送完报文后自动关闭当前连接。
     *
     * @param channel
     */
    public static void sendCloseChannelMsg(Channel channel, String reason) {
        channel.writeAndFlush(new CloseWebSocketFrame(WebSocketMsgContants.MSG_CLOSE_NORMAL, reason)).addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * token无效或过期后服务器发送主动关闭连接的消息报文。该方法会在发送完报文后自动关闭当前连接。
     *
     * @param channel
     */
    public static void sendCloseChannelMsgAfterTokenExpired(Channel channel, String reason) {
        channel.writeAndFlush(new CloseWebSocketFrame(WebSocketMsgContants.MSG_CLOSE_AFTER_TOKEN_EXPIRED, reason)).addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * 给景区监控系统web前端发送景区运营状态被修改的消息。
     *
     * @param scenicId
     * @param operationStatus
     */
    public static void sendScenicOperaStatusChangedMsg(Channel channel, Long scenicId, String operationStatus) {
        sendMsg(channel, WebSocketMsgContants.MSG_SCENIC_OPERA_STATUS_CHANGE, "", new ScenicOperaStatusChangedMsg(scenicId, operationStatus));
    }

    /**
     * 通知小程序客户端订单(包含计时计费和套餐计费订单)开始计费。
     */
    public static void notifyMiniClientOrderBeginCharging(Channel channel, OrderBeginChargeMsg orderBeginChargeMsg) {
        log.info("通知小程序客户端订单(包含计时计费和套餐计费订单)开始计费!");
        sendMsg(channel, WebSocketMsgContants.MSG_ORDER_BEGIN, "订单开始计费", orderBeginChargeMsg);
    }

    /**
     * 通知小程序客户端停止计时计费订单计费。
     *
     * @param channel
     * @param jishiOrderEndChargeMsg
     */
    public static void notifyClientSideJishiOrderStopCharging(Channel channel, JishiOrderEndChargeMsg jishiOrderEndChargeMsg) {
        log.info("通知小程序客户端停止计时计费订单计费!");
        sendMsg(channel, WebSocketMsgContants.MSG_JISHI_ORDER_END, "计时计费订单结束计费", jishiOrderEndChargeMsg);
    }

    /**
     * 通知小程序客户端停止套餐计费订单计费。
     *
     * @param channel
     * @param taocanOrderEndChargeMsg
     */
    public static void notifyClientSideTaocanOrderStopCharging(Channel channel, TaocanOrderEndChargeMsg taocanOrderEndChargeMsg) {
        log.info("通知小程序客户端停止套餐计费订单计费!");
        sendMsg(channel, WebSocketMsgContants.MSG_TAOCAN_ORDER_END, "套餐计费订单结束计费", taocanOrderEndChargeMsg);
    }

    /**
     * 通知小程序客户端停止拼团订单计费。
     *
     * @param channel
     * @param pintuanOrderEndMsg
     */
    public static void notifyClientSidePintuanOrderStopCharging(Channel channel, PintuanOrderEndMsg pintuanOrderEndMsg) {
        sendMsg(channel, WebSocketMsgContants.MSG_PINTUAN_ORDER_END, "拼团订单结束计费", pintuanOrderEndMsg);
    }

    /**
     * 通知小程序客户端拼团状态发生改变。
     *
     * @param channel
     * @param pintuanStatusMsg
     */
    public static void notifyClientSidePintuanStatusModified(Channel channel, PintuanStatusMsg pintuanStatusMsg) {
        sendMsg(channel, WebSocketMsgContants.MSG_PINTUAN_STATUS_MODIFIED, "拼团状态信息", pintuanStatusMsg);
    }

//    ========================================================================================================================
//    ================================================ 如下为给景区管理发送消息 =================================================================

    /**
     * 给景区管理员发送消息。
     *
     * @param scenicId
     * @param msgObject
     */
    public static void sendMsgToScenicAdmin(Long scenicId, Object msgObject) {
        if (Objects.isNull(scenicId)) {
//            log.warn("scenicId为空，发送消息给景区管理员失败！");
            return;
        }
        if (Objects.isNull(msgObject)) {
//            log.error("消息对象msgObject为空，发送消息给景区管理员失败！");
            return;
        }
        List<Channel> scenicWebsocketChannelList = WebSocketChannelManager.getScenicWebSocketChannelListByScenicId(scenicId);
        if (Objects.isNull(scenicWebsocketChannelList) || scenicWebsocketChannelList.size() == 0) {
//            log.error("当前景区未连接，发送消息给景区管理员失败！");
            return;
        }
        if (msgObject instanceof ScenicOperationDynamicInfo) {
            sendScenicOperationDynamicInfoUpdatedMsg(scenicWebsocketChannelList, (ScenicOperationDynamicInfo) msgObject);
        } else if (msgObject instanceof BoatOperationStatus) {
            sendBoatStatusUpdatedMsg(scenicWebsocketChannelList, (BoatOperationStatus) msgObject);
        } else if (msgObject instanceof BatteryInfo) {
            sendBoatBatteryInfoToScenicMonitorCenter(scenicWebsocketChannelList, (BatteryInfo) msgObject);
        } else if (msgObject instanceof BoatRealtimeStatusInfo) {
            sendBoatRealtimeStatusInfoToScenicMonitorCenter(scenicWebsocketChannelList, (BoatRealtimeStatusInfo) msgObject);
        } else if (msgObject instanceof BoatAlarmInfo) { //船只报警信息
            sendAlarmStatusInfoMsg(scenicWebsocketChannelList, (BoatAlarmInfo) msgObject);
        }
    }

    /**
     * 给景区监控中心发送景区历史累计总收入被更新的信息。
     *
     * @param scenicId
     */
    public static void sendScenicAccumulatedTotalIncomeUpdatedMsg(Long scenicId) {
        Objects.requireNonNull(scenicId);

        List<Channel> channelList = WebSocketChannelManager.getScenicWebSocketChannelListByScenicId(scenicId);
        if (Objects.isNull(channelList) || channelList.size() == 0) {
            return;
        }

        for (int i = 0; i < channelList.size(); i++) {
            ScenicAccumulatedTotalIncomeUpdatedInfo accumulatedTotalIncomeUpdatedInfo = new ScenicAccumulatedTotalIncomeUpdatedInfo();
            accumulatedTotalIncomeUpdatedInfo.setScenicId(scenicId);
            IScenicDailyIncomeService scenicDailyIncomeService = SpringUtils.getBean(IScenicDailyIncomeService.class);
            double accumulatedTotalIncome = scenicDailyIncomeService.getAccumulatedTotalIncome(scenicId);
            accumulatedTotalIncomeUpdatedInfo.setAccumulatedTotalIncome(accumulatedTotalIncome);
            sendMsg(channelList.get(i), WebSocketMsgContants.MSG_SCENIC_ACCUMULATED_TOTAL_INCOME_UPDATED, "景区历史累计总收入更新", accumulatedTotalIncomeUpdatedInfo);
        }
    }

    /**
     * 给景区监控中心发送船只实时的惯导信息。
     *
     * @param scenicWebsocketChannel
     * @param navigationInfo
     */
    public static void sendNavigationInfoToScenicMonitorCenter(Channel scenicWebsocketChannel, NavigationInfo navigationInfo) {
        sendMsg(scenicWebsocketChannel, WebSocketMsgContants.MSG_NAVIGATION_INFO, "船只惯导信息", navigationInfo);
    }

    /**
     * 给景区监控中心发送船只实时的状态信息(包含惯导和泡泡豚船等相关信息数据)。
     *
     * @param realtimeStatusInfo
     */
    public static void sendBoatRealtimeStatusInfoToScenicMonitorCenter(List<Channel> scenicWebsocketChannelList, BoatRealtimeStatusInfo realtimeStatusInfo) {
        for (int i = 0; i < scenicWebsocketChannelList.size(); i++) {
            sendMsg(scenicWebsocketChannelList.get(i), WebSocketMsgContants.MSG_PAOPAOTUN_BOAT_INFO, "泡泡豚船只实时状态更新信息", realtimeStatusInfo);
        }
    }

    public static void sendBoatBatteryInfoToScenicMonitorCenter(List<Channel> scenicWebsocketChannelList, BatteryInfo batteryInfo) {
        for (int i = 0; i < scenicWebsocketChannelList.size(); i++) {
            sendMsg(scenicWebsocketChannelList.get(i), WebSocketMsgContants.MSG_BOAT_BATTERY_STATUS_INFO, "泡泡豚船只电池状态更新信息", batteryInfo);
        }
    }

    public static void sendBoatStatusUpdatedMsg(List<Channel> scenicWebsocketChannelList, BoatOperationStatus boatOperationStatus) {
        for (int i = 0; i < scenicWebsocketChannelList.size(); i++) {
            sendMsg(scenicWebsocketChannelList.get(i), WebSocketMsgContants.MSG_BOAT_OPERATION_STATUS_UPDATED, "泡泡豚船只运营状态更新信息", boatOperationStatus);
        }
    }

    public static void sendScenicOperationDynamicInfoUpdatedMsg(List<Channel> scenicWebsocketChannelList, ScenicOperationDynamicInfo scenicOperationDynamicInfo) {
        for (int i = 0; i < scenicWebsocketChannelList.size(); i++) {
            sendMsg(scenicWebsocketChannelList.get(i), WebSocketMsgContants.MSG_SCENIC_OPERATION_DYNAMIC_INFO_UPDATED, "景区运营动态信息更新", scenicOperationDynamicInfo);
        }
    }

    /**
     * 给景区监控中心发送船只报警状态信息(包含电子围栏等报警信息)。
     *
     * @param boatAlarmInfo
     */
    public static void sendAlarmStatusInfoMsg(List<Channel> scenicWebsocketChannelList, BoatAlarmInfo boatAlarmInfo) {
        for (int i = 0; i < scenicWebsocketChannelList.size(); i++) {
            sendMsg(scenicWebsocketChannelList.get(i), WebSocketMsgContants.MSG_SEND_BOAT_ALARM_STATUS_INFO, "泡泡豚船只报警状态信息", boatAlarmInfo);
        }
    }


}
